package main

import "os"
import "image"
import "image/color"
import "image/draw"
import "fmt"
import "io"
import "io/ioutil"

import "code.google.com/p/ehedgehog/xtinker/buffer"
import "code.google.com/p/ehedgehog/xtinker/keys"

import "code.google.com/p/jamslam-freetype-go/freetype"
import "code.google.com/p/jamslam-freetype-go/freetype/raster"
import "code.google.com/p/jamslam-freetype-go/freetype/truetype"
import "code.google.com/p/x-go-binding/xgb"

func main() {
	c, err := xgb.Dial(os.Getenv("DISPLAY"))
	if err == nil {
		eventLoop(c)
	} else {
		panic(err)
	}
}

type Editing struct {
	Line, Col    int32
	Lines, Cols  int32
	scrollOffset int32
}

func displayCursor(w *Window, b buffer.Buffer, e *Editing) *xgb.GetImageReply {
	image := saveCursor(w, b, e)
	drawCursor(w, b, e)
	return image
}

func saveCursor(w *Window, b buffer.Buffer, e *Editing) *xgb.GetImageReply {
	const pixelsAcross, pixelsDown = 10, 18
	const cursorWidth, cursorHeight = 4, 18
	x, y := e.Col*pixelsAcross, e.Line*pixelsDown
	image, err := w.con.GetImage(
		xgb.ImageFormatZPixmap,
		w.win,
		int16(x),
		int16(y),
		cursorWidth,
		cursorHeight,
		0xffffff)
	panicUnlessNil(err)
	return image
}

func drawCursor(w *Window, b buffer.Buffer, e *Editing) {
	const pixelsAcross, pixelsDown = 10, 18
	const cursorWidth, cursorHeight = 4, 18
	x, y := int16(e.Col*pixelsAcross), int16(e.Line*pixelsDown)
	points := []xgb.Point{{x, y}, {x, y + pixelsDown}}
	w.con.PolyLine(0, w.win, w.gc, points)
}

func hideCursor(w *Window, b buffer.Buffer, e *Editing, image *xgb.GetImageReply) {
	const pixelsAcross, pixelsDown = 10, 18
	const cursorWidth, cursorHeight = 4, 18
	x, y := int16(e.Col*pixelsAcross), int16(e.Line*pixelsDown)
	w.con.PutImage(
		xgb.ImageFormatZPixmap,
		w.win,
		w.gc,
		cursorWidth, cursorHeight,
		int16(x),
		int16(y),
		0,
		image.Depth,
		image.Data)
}

func eventLoop(con *xgb.Conn) {
	b := buffer.NewBuffer()
	km, err := con.GetKeyboardMapping(8, 255-8+1)
	panicUnlessNil(err)
	// text := ""
	e := &Editing{}
	w := createWindow(con, 600, 400)
	x, y := int16(0), int16(0)
	bufferUp(b, e, 'x')
	count := 0
	for {
		count += 1
		fmt.Println("count:", count)

		var reply xgb.Event
		var err error

		if count < 5 {
			reply, err = con.WaitForEvent()
		} else {
			saved := displayCursor(w, b, e)
			reply, err = con.WaitForEvent()
			hideCursor(w, b, e, saved)
		}

		// fmt.Printf(">> %T: %v\n", reply, reply)
		_ = w
		_ = reply
		if err == io.EOF {
			return
		} else if err != nil {
			fmt.Println("OOPS:", err)
		}
		switch event := reply.(type) {
		case xgb.MotionNotifyEvent:
			x, y = event.EventX, event.EventY

		case xgb.KeyPressEvent:
			// deferred

		case xgb.KeyReleaseEvent:
			shift := int(event.State & 1)
			d := int(event.Detail - 8)
			keysym := km.Keysyms[d*int(km.KeysymsPerKeycode)+shift]
			key := int32(keysym)
			if event.State&4 != 0 {
				key = key - 'a' + 1
			}
			fmt.Println("key:", key)
			bufferUp(b, e, rune(key))
			x, y = x+0, y+0
			refresh(w, e, b)

		case xgb.ButtonPressEvent:
			// something at this place

		case xgb.ButtonReleaseEvent:
			fmt.Println("e:", e)
			// for i := int32(0); i < e.Lines+1; i += 1 {
			// 	fmt.Println("line", i, "is", b.Get(i))
			// 	PutString(w, b.Get(i), x, y+20*int16(i))
			// }

		case xgb.ConfigureNotifyEvent:
			// resize event.Width, event.Height
			refresh(w, e, b)

		case xgb.ExposeEvent:
			if event.Count == 0 {
				// this is the complete expose event
				refresh(w, e, b)
			}

		default:
			fmt.Printf("unhandled %T event %v\n", event, event)
		}
	}
}

func refresh(w *Window, e *Editing, b buffer.Buffer) {
	for i := int32(0); i < e.Lines+1; i += 1 {
		fmt.Println("line", i, "is", b.Get(i))
		PutString(w, b.Get(i), 0, 0+20*int16(i))
	}
}

func bufferUp(b buffer.Buffer, e *Editing, ch rune) {
	switch ch {
	case keys.XK_Home:

	case keys.XK_End:
	case keys.XK_Up:
	case keys.XK_Down:

	case keys.XK_Left:
		if e.Col > 0 {
			e.Col -= 1
		}

	case keys.XK_Right:
	case keys.XK_Page_Up:
	case keys.XK_Page_Down:
	case keys.XK_Delete:
	case keys.XK_BackSpace:

	case keys.XK_Return:
		b.Split(e.Line, e.Col)
		e.Lines += 1
		e.Line += 1
		e.Col = 0

	case 'l' - 'a' + 1:
	case 'j' - 'a' + 1:
	case 'd' - 'a' + 1:
	case keys.XK_F1:

	default:
		if ' ' <= ch && ch <= 126 {
			b.Insert(ch, e.Line, e.Col)
			e.Col += 1
		}
	}
}

func PutString(w *Window, message string, x, y int16) {
	image := w.WriteStringToImage(message)
	w.DisplayImage(image, x, y)
}

func ToInt(n raster.Fix32) int {
	return int(n >> 8)
}

var font = loadFont()

func loadFont() *truetype.Font {
	filename := "/usr/share/fonts/liberation/LiberationMono-Regular.ttf"
	fontbytes, err := ioutil.ReadFile(filename)
	panicUnlessNil(err)
	tfont, err := freetype.ParseFont(fontbytes)
	panicUnlessNil(err)
	return tfont
}

func (win *Window) WriteStringToImage(message string) *image.RGBA {
	font := font

	const FS = 12
	const DPI = 72

	context := freetype.NewContext()
	context.SetFontSize(FS)
	context.SetFont(font)

	BG := image.NewUniform(color.RGBA{0xdd, 0xdd, 0xdd, 0})
	FG := image.NewUniform(color.RGBA{0, 0, 0, 255})

	context.SetDPI(DPI)
	context.SetSrc(FG)

	w, h, err := context.MeasureString(message)
	panicUnlessNil(err)
	rgba := image.NewRGBA(image.Rect(0, 0, ToInt(w), ToInt(h)))

	if true {
		draw.Draw(rgba, rgba.Bounds(), BG, image.ZP, draw.Over)
	}
	if false {
		draw.Draw(rgba, rgba.Bounds(), FG, image.ZP, draw.Over)
	}

	context.SetDst(rgba)
	context.SetClip(rgba.Bounds())
	pt, err := context.DrawString(message, raster.Point{0, FS * 256})
	_ = pt
	panicUnlessNil(err)
	// fmt.Println(rgba)
	return rgba
}

func panicUnlessNil(err error) {
	if err != nil {
		panic(err)
	}
}

type Window struct {
	con *xgb.Conn
	win xgb.Id
	gc  xgb.Id
}

func (w *Window) DisplayImage(rgba *image.RGBA, x, y int16) {

	bounds := rgba.Bounds()

	width, height := bounds.Dx(), bounds.Dy()

	const pad = uint8(0)

	const depth = uint8(24)

	const bytesPerPixel = 4

	size := width * height * bytesPerPixel

	bytes := make([]byte, size)
	bindex := 0
	for _, pixel := range rgba.Pix {
		bytes[bindex] = pixel
		bindex += 1
	}

	w.con.PutImage(xgb.ImageFormatZPixmap,
		w.win, w.gc,
		uint16(width), uint16(height),
		x, y,
		pad, depth, bytes)

}

func createWindow(con *xgb.Conn, width, height int16) *Window {
	win := con.NewId()
	s := con.DefaultScreen()
	gc := con.NewId()
	con.CreateWindow(0, win, s.Root, 50, 50, uint16(width), uint16(height), 0, 0, 0, 0, nil)
	con.ChangeWindowAttributes(win, xgb.CWBackPixel|xgb.CWEventMask,
		[]uint32{
			s.WhitePixel,
			xgb.EventMaskExposure | xgb.EventMaskKeyRelease | xgb.EventMaskKeyPress |
				xgb.EventMaskButtonPress | xgb.EventMaskButtonRelease |
				xgb.EventMaskPointerMotion | xgb.EventMaskStructureNotify,
		})
	con.ChangeProperty(
		xgb.PropModeReplace,
		win,
		xgb.Id(39), // pre-defined atom WM_NAME
		xgb.Id(31), // pre-defined atom STRING
		8,          // units are bytes
		[]byte("Tide"),
	)
	//
	con.CreateGC(gc, win, xgb.GCBackground|xgb.GCForeground, []uint32{s.BlackPixel, s.WhitePixel})
	con.MapWindow(win)
	//
	wp := &Window{
		con: con,
		win: win,
		gc:  gc}
	// body.Parent(wp)
	return wp
}
